/**
 * 
 */
package nus.mtech.bill;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import nus.mtech.call.CallManager;
import nus.mtech.constant.Constant;
import nus.mtech.customer.Customer;
import nus.mtech.customer.CustomerManager;
import nus.mtech.subscription.CableTV;
import nus.mtech.subscription.DigitalVoice;
import nus.mtech.subscription.ExtraFeature;
import nus.mtech.subscription.ExtraFeature.ExtraFeatureType;
import nus.mtech.subscription.MobileVoice;
import nus.mtech.subscription.Subscription;
import nus.mtech.subscription.SubscriptionManager;
import nus.mtech.util.DateUtil;

/**
 * @author Chew Xunmao
 * 
 */
public class BillManager {

	private static BillManager BillManagerInstance;
	// String is account number, value is bill object
	private HashMap<String, ArrayList<Bill>> mapOfBills = new HashMap<String, ArrayList<Bill>>();
	private HashMap<String, Date> monthlyBillFlagMap;
	private static int billNo = 1;

	public static BillManager getBillManagerInstance() {
		if (BillManagerInstance != null) {
			return BillManagerInstance;

		} else {
			BillManagerInstance = new BillManager();
			return BillManagerInstance;
		}
	}

	private BillManager() {
		monthlyBillFlagMap = new HashMap<String, Date>();
	}

	public Bill createBill(String accountNo, Date billDate) {
		Bill lNewBill = new Bill();
		lNewBill.setAccountNo(accountNo);
		lNewBill.setBillDate(billDate);

		Calendar lCal = Calendar.getInstance();
		Date lDueDate = billDate;
		lCal.setTime(lDueDate);
		lCal.add(Calendar.MONTH, 1);
		lNewBill.setDueDate(lCal.getTime());

		calculateBill(accountNo, billDate, lNewBill);

		CustomerManager lMgr = CustomerManager.getCustomerManagerInstance();
		Customer lCustObj = lMgr.getCustomerByAccountNo(accountNo);

		lNewBill.setCustomerAddress(lCustObj.getCustBillingAddr());

		// addBill(accountNo, lNewBill);

		return lNewBill;
	}

	public void addBill(String accountNo, Bill newBill) {
		Integer lBillNo = getBillId();
		newBill.setBillNo(lBillNo.toString());

		if (mapOfBills.containsKey(accountNo)) {
			if (!isSameMonthBill(mapOfBills.get(accountNo), newBill))
				mapOfBills.get(accountNo).add(newBill);
		} else {
			ArrayList<Bill> lNewBillList = new ArrayList<Bill>();
			lNewBillList.add(newBill);
			mapOfBills.put(accountNo, lNewBillList);
		}
	}

	private boolean isSameMonthBill(ArrayList<Bill> listOfBills, Bill newBill) {
		boolean lRetResult = false;

		if (listOfBills != null) {
			for (Bill lPreviousBills : listOfBills) {
				if (newBill.getBillDate().compareTo(
						lPreviousBills.getBillDate()) == 0) {
					lRetResult = true;
					break;
				}
			}
		}
		return lRetResult;
	}

	public double calculateBill(String accountNo, Date billDate, Bill newBill) {
		double lTotalAmt = 0;
		SubscriptionManager lSubMgr = SubscriptionManager
				.getSubscriptionManagerInstance();
		ArrayList<Subscription> lListOfSubs = lSubMgr
				.getSubscription(accountNo);

		for (Subscription lSub : lListOfSubs) {
			if (lSub instanceof DigitalVoice) {

				DigitalVoice lTempDigitalVoice = (DigitalVoice) lSub;

				double lTotalMonthlyCharges = getMonthlyDigitalVoiceSubscriptionCharges(
						lTempDigitalVoice, newBill);
				double lTotalUsageCharges = 0;

				if (lTotalMonthlyCharges > 0)
					lTotalUsageCharges = getUsageChargesForDigitalVoice(
							lTempDigitalVoice, accountNo, billDate, newBill);

				newBill.setDigitalVoiceSubscriptionChargesSummary(lTotalMonthlyCharges);
				newBill.setDigitalVoiceUsageChargesSummary(lTotalUsageCharges);

				Long dvNo = lTempDigitalVoice.getTelephoneNo();
				newBill.setDigitalVoiceNumber(dvNo.toString());

				lTotalAmt += lTotalUsageCharges + lTotalMonthlyCharges;

			}
			if (lSub instanceof MobileVoice) {
				MobileVoice lTempMobileVoice = (MobileVoice) lSub;

				double lTotalMonthlySubCharges = getMonthlyMobileVoiceSubscriptionCharges(
						lTempMobileVoice, newBill);
				double lTotalUsageCharges = 0;

				if (lTotalMonthlySubCharges > 0)
					lTotalUsageCharges = getUsageChargesForMobileVoice(
							lTempMobileVoice, accountNo, billDate, newBill);

				newBill.setMobileVoiceSubscriptionChargesSummary(lTotalMonthlySubCharges);
				newBill.setMobileVoiceUsageCharges(lTotalUsageCharges);

				Long mvNo = lTempMobileVoice.getTelephoneNo();
				newBill.setMobileVoiceNumber(mvNo.toString());

				lTotalAmt += lTotalUsageCharges + lTotalMonthlySubCharges;

			}
			if (lSub instanceof CableTV) {

				CableTV lTempCableTV = (CableTV) lSub;
				if (lTempCableTV.getChannels() != null) {
					// do some computation
					int lCountOfChannels = lTempCableTV.getChannels()
							.size() - 3;
					double lMonthlySub = lTempCableTV.getCableTVRate()
							.getMonthlySubscriptionRate();
					double lExtraCableTVChannelsSub = lTempCableTV
							.getCableTVRate().getChannelRate()
							* lCountOfChannels;
					lTotalAmt += lMonthlySub + lExtraCableTVChannelsSub;

					newBill.setCableTVSubscriptionChargesSummary(lMonthlySub);
					newBill.setAdditionalCableTVChannelsCharges(lExtraCableTVChannelsSub);

					newBill.setNoOfCableTVChannels("" + lCountOfChannels);
					Double lChannelRate = lTempCableTV.getCableTVRate()
							.getChannelRate();
					
					newBill.setCableTVChannelRate(lChannelRate.toString());

				} else
					lTotalAmt += 0;
			}
		}
	    double gst = lTotalAmt * (newBill.getGST_APPLIED() / 100);
		lTotalAmt = lTotalAmt + gst;
		newBill.setTotalBill(lTotalAmt);
		newBill.setTotalGST(gst);
		return lTotalAmt;
	}

	private double getMonthlyMobileVoiceSubscriptionCharges(
			MobileVoice MobileVoiceCharge, Bill newBill) {
		double lTotalMonthlySubCharges = 0;

		double lMonthlySubscriptionMobileRate = MobileVoiceCharge
				.getMobileRate().getMonthlySubscriptionRate();

		double lMonthlySubscriptionRoamRate = MobileVoiceCharge.getMobileRate()
				.getMonthlySubRoamingRate();
		double lMonthlyDataRate = MobileVoiceCharge.getMobileRate()
				.getDataRate();

		lTotalMonthlySubCharges = lMonthlySubscriptionMobileRate
				+ lMonthlySubscriptionRoamRate + lMonthlyDataRate;

		for (ExtraFeature lExtFeature : MobileVoiceCharge
				.getMapOfExtraFeatures().values()) {
			if (!lExtFeature.isActiveStatus()) {
				if (!shouldBill(newBill.getBillDate(), lExtFeature.getEndDate())) {
					if (lExtFeature.getType() == ExtraFeatureType.Roaming)
						lMonthlySubscriptionRoamRate = 0;

					if (lExtFeature.getType() == ExtraFeatureType.DataService)
						lMonthlyDataRate = 0;
				}
			}

			if (!MobileVoiceCharge.isActiveStatus()) {
				if (!shouldBill(newBill.getBillDate(),
						MobileVoiceCharge.getEndDate())) {
					lMonthlySubscriptionMobileRate = 0;

				}
			}

			newBill.setDetailMobileVoiceChargeSubMobile(lMonthlySubscriptionMobileRate);
			newBill.setDetailMobileVoiceChargeSubRoaming(lMonthlySubscriptionRoamRate);
			newBill.setDetailMobileVoiceChargeSubDataServices(lMonthlyDataRate);
		}
		return lTotalMonthlySubCharges;

	}

	private double getMonthlyDigitalVoiceSubscriptionCharges(
			DigitalVoice DigitalVoiceCharge, Bill newBill) {
		double lTotalMonthlySubCharges = 0;

		double lMonthlySubscriptionMobileRate = DigitalVoiceCharge.getDvRate()
				.getMonthlySubscriptionRate();
		double lMonthlyCallTransferRate = DigitalVoiceCharge.getDvRate()
				.getCallTransferRate();

		if (!DigitalVoiceCharge.getExtraFeature().isActiveStatus()) {
			Date lBillDate = newBill.getBillDate();
			Date lTerminatedDate = DigitalVoiceCharge.getExtraFeature()
					.getEndDate();
			if (!shouldBill(lBillDate, lTerminatedDate)) {
				lMonthlyCallTransferRate = 0;
			}

		}
		if (!DigitalVoiceCharge.getStatus()) {
			if (!shouldBill(newBill.getBillDate(),
					DigitalVoiceCharge.getEndDate())) {
				lMonthlySubscriptionMobileRate = 0;
			}
		}

		lTotalMonthlySubCharges = lMonthlySubscriptionMobileRate
				+ lMonthlyCallTransferRate;

		newBill.setDetailDigitalVoiceSubCallTransfer(lMonthlyCallTransferRate);
		newBill.setDetailDigitalVoiceSubChargeLine(lMonthlySubscriptionMobileRate);

		return lTotalMonthlySubCharges;
	}

	private boolean shouldBill(Date billDate, Date terminatedDate) {
		boolean lReturnResult = false;
		Calendar lCal = Calendar.getInstance();
		lCal.setTime(billDate);

		int lBillDateMth = lCal.get(Calendar.MONTH);
		if (terminatedDate == null)
			return true;
		lCal.setTime(terminatedDate);
		int lTerminatedDateMth = lCal.get(Calendar.MONTH);
		if (lTerminatedDateMth == lBillDateMth
				|| lTerminatedDateMth > lBillDateMth)
			lReturnResult = true;

		if (lTerminatedDateMth < lBillDateMth)
			lReturnResult = false;

		return lReturnResult;

	}

	private double getUsageChargesForMobileVoice(MobileVoice mobileVoiceCharge,
			String accountNo, Date billDate, Bill newBill) {
		double lTotalUsageCharges = 0;
		CallManager lCallMgr = CallManager.getCallManagerInstance();

		double lIDDRate = mobileVoiceCharge.getMobileRate().getiDDRate();

		double lRoamRate = mobileVoiceCharge.getMobileRate().getRoamRate();

		double lLocalCallRate = mobileVoiceCharge.getMobileRate()
				.getLocalCallRate();

		int lTimeDurationIDD = lCallMgr.calculateUsageByCallType(accountNo,
				billDate, Constant.CALLTYPE_IDD);
		int lTimeDurationRoam = lCallMgr.calculateUsageByCallType(accountNo,
				billDate, Constant.CALLTYPE_ROAM);
		int lTimeDurationLocalCall = lCallMgr.calculateUsageByCallType(
				accountNo, billDate, Constant.CALLTYPE_LOCALCALLS);

		double lIDDUsageCharges = lIDDRate * lTimeDurationIDD;
		double lRoamUsageCharges = lRoamRate * lTimeDurationRoam;
		double lLocalCallUsageCharges = lLocalCallRate * lTimeDurationLocalCall;

		newBill.setDetailMobileVoiceUsageChargeIDDCalls(lIDDUsageCharges);
		newBill.setDetailMobileVoiceUsageChargeRoamingCalls(lRoamUsageCharges);
		newBill.setDetailMobileVoiceUsageChargeLocalCalls(lLocalCallUsageCharges);

		lTotalUsageCharges = lIDDUsageCharges + lRoamUsageCharges
				+ lLocalCallUsageCharges;

		return lTotalUsageCharges;
	}

	private double getUsageChargesForDigitalVoice(
			DigitalVoice digitalVoiceCharge, String accountNo, Date billDate,
			Bill newBill) {
		double lTotalUsageCharges = 0;

		CallManager lCallMgr = CallManager.getCallManagerInstance();

		int lTimeDurationOfLocalCalls = lCallMgr.calculateUsageByCallType(
				accountNo, billDate, Constant.CALLTYPE_LOCALCALLS);
		int lTimeDurationOfIDDCalls = lCallMgr.calculateUsageByCallType(
				accountNo, billDate, Constant.CALLTYPE_IDD);

		double lLocalCallCharges = digitalVoiceCharge.getDvRate()
				.getLocalCallRate() * lTimeDurationOfLocalCalls;

		double lIDDCallsCharges = digitalVoiceCharge.getDvRate()
				.getIDDCallRate() * lTimeDurationOfIDDCalls;

		newBill.setDetailDigitalVoiceUsageChargeLocalCalls(lLocalCallCharges);
		newBill.setDetailDigitalVoiceUsageChargeIDDCalls(lIDDCallsCharges);
		lTotalUsageCharges = lLocalCallCharges + lIDDCallsCharges;

		return lTotalUsageCharges;

	}

	public ArrayList<Bill> getAllBillsByMonth(int searchMonth, Date lSearchDate) {
		ArrayList<Bill> lListOfBills = new ArrayList<Bill>();
		Set<String> lKeySet = mapOfBills.keySet();
		searchMonth = searchMonth - 1;
		for (String lAccountNo : lKeySet) {
			ArrayList<Bill> lListOfBillByAccount = mapOfBills.get(lAccountNo);
			for (Bill lBill : lListOfBillByAccount) {
				Date billDate = lBill.getBillDate();
				Calendar lCal = Calendar.getInstance();
				lCal.setTime(billDate);
				int lBillMonth = lCal.get(Calendar.MONTH);

				if (lBillMonth != searchMonth) {
					if (!isBillAlreadyCreatedForTheMonth(searchMonth,
							lAccountNo)) {
						Bill lNewBill = createBill(lAccountNo, billDate);
						addBill(lAccountNo, lNewBill);
					}

				} else
					lListOfBills.add(lBill);
			}
		}
		return lListOfBills;
	}

	private boolean isBillAlreadyCreatedForTheMonth(int monthOfSearch,
			String accountNo) {
		boolean lReturnResult = false;
		ArrayList<Bill> lListOfBillsFromAccount = mapOfBills.get(accountNo);

		for (Bill lTempBill : lListOfBillsFromAccount) {
			Date lBillDate = lTempBill.getBillDate();
			Calendar lCal = Calendar.getInstance();

			lCal.setTime(lBillDate);
			int lBillMonth = lCal.get(Calendar.MONTH);
			if (lBillMonth == monthOfSearch) {
				lReturnResult = true;
				break;
			}
		}
		return lReturnResult;
	}

	public Bill getBillByMonth(int searchMonth, Date lSearchDate,
			String accountNo) {

		Bill lReturnBillResult = new Bill();
		searchMonth = searchMonth - 1;

		ArrayList<Bill> lListOfBillByAccount = new ArrayList<Bill>();
		lListOfBillByAccount = mapOfBills.get(accountNo);
		if (!checkAllBill(lListOfBillByAccount, searchMonth))
			return createBill(accountNo, lSearchDate);
		else {
			for (Bill lBill : lListOfBillByAccount) {
				Date lBillDate = lBill.getBillDate();
				Calendar lCal = Calendar.getInstance();
				lCal.setTime(lBillDate);

				int lGetBillMth = lCal.get(Calendar.MONTH);
				if (lGetBillMth == searchMonth) {
					lReturnBillResult = lBill;
					break;
				}
			}
		}
		return lReturnBillResult;
	}

	private boolean checkAllBill(ArrayList<Bill> listOfBills, int searchMonth) {
		boolean lFound = false;
		searchMonth = searchMonth - 1;
		if (listOfBills == null || listOfBills.size() < 0)
			return false;
		for (Bill lBill : listOfBills) {
			Date billDate = lBill.getBillDate();
			Calendar lCal = Calendar.getInstance();
			lCal.setTime(billDate);
			int lBillMonth = lCal.get(Calendar.MONTH);
			if (lBillMonth == searchMonth) {
				lFound = true;
				break;
			}
		}
		return lFound;

	}

	public ArrayList<Bill> getAllBillsByAccountNumber(String accountNo) {

		if (mapOfBills.containsKey(accountNo))
			return mapOfBills.get(accountNo);
		else
			return null;
	}

	private void printBill(Bill bill) {
		if (bill.isPrinted())
			return;
		try {
			TextBillPrinter.printBill(bill, getBillFileName(bill));
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private String getBillFileName(Bill bill) {
		return Constant.BILL_DIR
				+ File.separator
				+ bill.getAccountNo()
				+ "."
				+ DateUtil.formatDate(bill.getBillDate(),
						Constant.MONTHLY_BILL_DATE_KEY_FORMAT);
	}

	public int getBillId() {
		return billNo++;
	}

	public void setBillId(int billNo) {
		BillManager.billNo = billNo;
	}

	public HashMap<String, ArrayList<Bill>> getMapOfBills() {
		return mapOfBills;
	}

	public void setMapOfBills(HashMap<String, ArrayList<Bill>> mapOfBills) {
		this.mapOfBills = mapOfBills;
	}

	// by guo yu
	public boolean generateMonthlyBill(Date date) {
		File file = new File(Constant.BILL_DIR);
		if(!file.exists()){
			file.mkdir();
		}
		
		String dateKey = DateUtil.formatDate(date,
				Constant.MONTHLY_BILL_DATE_KEY_FORMAT);
		if (monthlyBillFlagMap.containsKey(dateKey))
			return false;
		List<Bill> billList = computeMonthlyBill(date);
		for (Bill bill : billList)
			printBill(bill);
		monthlyBillFlagMap.put(dateKey, new Date());
		return true;

	}

	private List<Bill> computeMonthlyBill(Date date) {
		ArrayList<Bill> lListOfBills = new ArrayList<Bill>();
		Collection<Account> accountList = AccountManager.getInstance()
				.getAllAccounts();
		for (Account accountNo : accountList) {
			Bill bill = createBill(accountNo.getAccountNo(), date);
			lListOfBills.add(bill);
			addBill(bill.getAccountNo(), bill);
		}
		return lListOfBills;
	}

	public Bill searchBill(String accountNo, Date date) {
		List<Bill> billList = mapOfBills.get(accountNo);
		for (Bill b : billList) {
			if (DateUtil.sameMonth(b.getBillDate(), date)) {
				return b;
			}
		}
		return null;
	}

	public String getBillStatement(String accountNo, Date date) {
		List<Bill> billList = mapOfBills.get(accountNo);
		if (billList == null)
			return null;
		for (Bill b : billList) {
			if (DateUtil.sameMonth(b.getBillDate(), date)) {
				return TextBillPrinter.getBillStatement(b);
			}
		}
		return null;
	}

	public List<Bill> searchBills(String accountNo) {
		if (mapOfBills.containsKey(accountNo))
			return mapOfBills.get(accountNo);
		return new ArrayList<Bill>();
	}

	public HashMap<String, Date> getMonthlyBillFlagMap() {
		return monthlyBillFlagMap;
	}

	public void setMonthlyBillFlagMap(HashMap<String, Date> monthlyBillFlagMap) {
		this.monthlyBillFlagMap = monthlyBillFlagMap;
	}

}
